
const VERTEX_SHADER_SCRIPT = `
varying vec2 vUv;
void main(){
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

class GlslPainter {
  context = null;
  THREE = null;
  width = 0;
  height = 0;
  /** @type {string} */
  shaderName = null;
  /** @type {string} */
  shaderText = null;
  /** @type {string} */
  domName = null;

  scene = null;
  camera = null;
  renderer = null;
  uniforms = null;

  mouseX = 0;
  mouseY = 0;

  iTime = 0; // 暂停时该数值不再增加
  resolving = [1, 1]; // 模糊倍数. 也就是 scale, 仅仅调整分辨率
  zIndex = 0;
  iResolution = [0, 0];
  baseTime = 0; // 暂停时该数值无效
  speed = 1; // 暂停时该数值无效
  paused = false;

  /**
   * @param {object} args
   * 传入参数有:  shaderName, shaderText, domName
   */
  constructor(context, args) {
    this.context = context;
    this.shaderName = args.shaderName;
    this.shaderText = args.shaderText;
    this.domName = args.domName;
    const THREE = this.THREE = args.THREE;

    if (args.resolving != null && args.resolving instanceof Array && args.resolving.length === 2) {
      this.resolving = args.resolving.concat();
    }
    this.zIndex = (args.zIndex != null) ? args.zIndex : this.zIndex;
    
    const divMain = document.getElementById(this.domName);
    this.width = divMain.clientWidth;
    this.height = divMain.clientHeight;
    const w = this.width, h = this.height;
    const sx = this.resolving[0], sy = this.resolving[1];
    
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera( 75, w / h, 0.1, 1000 );
    this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    this.renderer.setSize( w / sx, h / sy );
    /** @type {HTMLCanvasElement} */ let canvasDom = this.renderer.domElement;

    if (sx != 1 || sy != 1) {
      canvasDom.style.transform = `translateX(${(1-1/sx)*w/2}px) translateY(${(1-1/sy)*h/2}px) scale(${sx},${sy})`;
    }
    canvasDom.style.zIndex = this.zIndex + "";
    divMain.appendChild( canvasDom );

    if (this.shaderText) {
      this.beginRender(VERTEX_SHADER_SCRIPT, this.shaderText);
    } else {
      this.fetchResourceSchema()
          .then(fragmentPath => this.fetchShaderScript(fragmentPath))
          .then((Shaders) => this.beginRender(Shaders[0], Shaders[1]));
    }
  }

  fetchResourceSchema() {
    return fetch(this.context.assetPath('glsl/glsl_res.json'))
        .then(res => res.json())
        .then(schema => schema[this.shaderName].fragmentPath);
  }

  fetchShaderScript(fragmentPath) {
    const path = this.context.assetPath(fragmentPath);
    return Promise.all(
      [
        Promise.resolve(VERTEX_SHADER_SCRIPT),
        fetch(path).then(res => res.text())
      ]
    );
  }

  /**
   * @param {string} vertexShader 
   * @param {string} fragmentShader 
   */
  beginRender(vertexShader, fragmentShader) {
    console.log(vertexShader, fragmentShader);
    const {THREE} = this;

    // 不同 shaders 特殊的内容
    this.iTime = 0;
    this.iResolution = [this.width / this.resolving[0], this.height / this.resolving[1]];
    const uniforms = this.uniforms = {
      iTime : { value: this.iTime },
      iResolution: { value: new THREE.Vector3( this.width / this.resolving[0], this.height / this.resolving[1], 1) }, // TODO 如果这样可能不是方形场. ( this.width, this.width, 1) 是方形场
      iMouse: { value: new THREE.Vector2( this.mouseX, this.mouseY ) },
    };

    const shadertoy = new THREE.ShaderMaterial({
      vertexShader,
      fragmentShader,
      uniforms,
      side : THREE.DoubleSide,
    });

    // mouse
    this.renderer.domElement.onmousedown = (e) => {
      this.setMouse(e.offsetX / this.width, e.offsetY / this.height);
    }
    this.renderer.domElement.onmousemove = (e) => {
      if (e.buttons) {
        this.setMouse(e.offsetX / this.width, e.offsetY / this.height);
      }
    }

    const box = new THREE.Mesh(
      new THREE.BoxGeometry(100,100,100),
      shadertoy
    );
    this.scene.add(box);

    this.baseTime = Date.now();
    this.render();
  }

  render() {
    requestAnimationFrame(this.render.bind(this));
    if (this.paused || this.speed == 0) {
      return;
    }
    this.iTime = (Date.now() - this.baseTime) / 1000 * this.speed;
    if (this.iTime < 0) {
      this.iTime = 0;
    }
    this.uniforms.iTime.value = this.iTime;
    this.renderer.render( this.scene, this.camera );
  }

  collectInfo() {
    const { iTime, iResolution, speed, paused, mouseX, mouseY } = this;
    return {
      iTime, iResolution, speed, paused, mouseX, mouseY
    };
  }

  /**
   * @param {number} speed -10~10
   */
  setSpeed(speed) {
    this.paused = false;
    this.speed = speed;
    this.baseTime = Date.now() - this.iTime * 1000 / this.speed;
  }

  pause() {
    this.paused = true;
  }

  setMouse(mouseX, mouseY) {
    this.mouseX = mouseX;
    this.mouseY = mouseY;
    this.uniforms.iMouse.value.x = ( this.width * mouseX) | 0;
    this.uniforms.iMouse.value.y = (this.height * mouseY) | 0;

    if (this.paused) {
      this.renderer.render( this.scene, this.camera );
    }
  }
}

export default GlslPainter;

